package com.aerozhonghuan.archive.service.impl;

import com.aerozhonghuan.archive.config.DynamicDataSourceHolder;
import com.aerozhonghuan.archive.dto.VerificationResult;
import com.aerozhonghuan.archive.entity.DataRecord;
import com.aerozhonghuan.archive.entity.MigrationConfig;
import com.aerozhonghuan.archive.mapper.ArchiveMapper;
import com.aerozhonghuan.archive.mapper.ArchiveMigrationConfigMapper;
import com.aerozhonghuan.archive.mapper.SourceMapper;
import com.aerozhonghuan.archive.mapper.SourceTableMetaMapper;
import com.aerozhonghuan.archive.service.DataConsistencyService;
import com.aerozhonghuan.archive.vo.MinMaxVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class DataConsistencyServiceImpl implements DataConsistencyService {

    private static final Logger logger = LoggerFactory.getLogger(DataConsistencyServiceImpl.class);

    private final SourceMapper sourceMapper;
    private final ArchiveMapper archiveMapper;
    private final ArchiveMigrationConfigMapper migrationConfigMapper;
    private final SourceTableMetaMapper tableMetaMapper;

    public DataConsistencyServiceImpl(SourceMapper sourceMapper,
                                      ArchiveMapper archiveMapper,
                                      ArchiveMigrationConfigMapper migrationConfigMapper,
                                      SourceTableMetaMapper tableMetaMapper) {
        this.sourceMapper = sourceMapper;
        this.archiveMapper = archiveMapper;
        this.migrationConfigMapper = migrationConfigMapper;
        this.tableMetaMapper = tableMetaMapper;
    }

    /**
     * 校验记录数一致性
     */
    @Override
    public List<VerificationResult> verifyCountConsistency(Date currentTime) {

        List<MigrationConfig> configs = migrationConfigMapper.getAllEnabled();
        List<VerificationResult> resultList = new ArrayList<>();

        if (configs == null || configs.isEmpty()) {
            logger.warn("未查询到启用的迁移配置记录。");
            return resultList;
        }

        for (MigrationConfig config : configs) {
            VerificationResult result = new VerificationResult();
            StringBuilder message = new StringBuilder();
            result.setSourceTableName(config.getSourceTableName());
            logger.info("开始行数校验配置记录: {}", config);

            // 校验关键字段
            if (config.getLastCreateTime() == null ||
                    config.getSourceDbName() == null ||
                    config.getSourceTableName() == null ||
                    config.getTargetDbName() == null ||
                    config.getTargetTableName() == null ||
                    config.getTimeStamp() == null ||
                    config.getTimeStamp().trim().isEmpty()) {
                message.append("配置记录中存在空字段，请检查：")
                        .append("sourceDbName=").append(config.getSourceDbName())
                        .append(", sourceTableName=").append(config.getSourceTableName())
                        .append(", targetDbName=").append(config.getTargetDbName())
                        .append(", targetTableName=").append(config.getTargetTableName())
                        .append(", timeStamp=").append(config.getTimeStamp())
                        .append(", lastCreateTime=").append(config.getLastCreateTime());
                result.setMessage(message.toString());
                logger.error(message.toString());
                resultList.add(result);
                continue;
            }

            String sourceDbName = config.getSourceDbName();
            String sourceTableName = config.getSourceTableName();
            String targetDbName = config.getTargetDbName();
            String targetTableName = config.getTargetTableName();
            String timeStampColumn = config.getTimeStamp();

            // 获取记录数
            long sourceCount = getSourceCount(sourceDbName, sourceTableName, timeStampColumn, currentTime, config.getSourceDatasource());
            long archiveCount = getArchiveCount(targetDbName, targetTableName, timeStampColumn, currentTime, config.getTargetDatasource());
            boolean countMatch = (sourceCount == archiveCount);
            message.append("源库记录数：").append(sourceCount)
                    .append("，归档库记录数：").append(archiveCount).append("。");
            if (sourceCount == archiveCount) {
                message.append("记录数一致。");
            } else {
                message.append("记录数不一致。");
            }
            logger.info("表 {}.{} 全量记录数比对: 源库={}，归档库={}", sourceDbName, sourceTableName, sourceCount, archiveCount);

            result.setCountMatch(countMatch);
            result.setMessage(message.toString());
            resultList.add(result);
        }
        return resultList;
    }

    /**
     * 校验关键字段MD5一致性
     */
    @Override
    public List<VerificationResult> verifyMd5Consistency(Date currentTime, int sampleCount) {

        List<MigrationConfig> configs = migrationConfigMapper.getAllEnabled();
        List<VerificationResult> resultList = new ArrayList<>();

        if (configs == null || configs.isEmpty()) {
            logger.warn("未查询到启用的迁移配置记录。");
            return resultList;
        }

        for (MigrationConfig config : configs) {
            VerificationResult result = new VerificationResult();
            StringBuilder message = new StringBuilder();
            result.setSourceTableName(config.getSourceTableName());
            logger.info("开始 MD5 校验配置记录: {}", config);

            // 校验关键字段
            if (config.getLastCreateTime() == null ||
                    config.getSourceDbName() == null ||
                    config.getSourceTableName() == null ||
                    config.getTargetDbName() == null ||
                    config.getTargetTableName() == null ||
                    config.getTimeStamp() == null ||
                    config.getTimeStamp().trim().isEmpty()) {
                message.append("配置记录中存在空字段：")
                        .append("sourceDbName=").append(config.getSourceDbName())
                        .append(", sourceTableName=").append(config.getSourceTableName())
                        .append(", targetDbName=").append(config.getTargetDbName())
                        .append(", targetTableName=").append(config.getTargetTableName())
                        .append(", timeStamp=").append(config.getTimeStamp())
                        .append(", lastCreateTime=").append(config.getLastCreateTime());
                result.setMessage(message.toString());
                logger.error(message.toString());
                resultList.add(result);
                continue;
            }

            String sourceDbName = config.getSourceDbName();
            String sourceTableName = config.getSourceTableName();

            // 获取源库实际字段列表
            List<String> actualColumns = getSourceColumns(sourceDbName, sourceTableName, config.getSourceDatasource());
            if (actualColumns == null || actualColumns.isEmpty()) {
                message.append("源库中表 ").append(sourceDbName).append(".").append(sourceTableName)
                        .append(" 不存在或无有效字段。");
                result.setMessage(message.toString());
                logger.error(message.toString());
                resultList.add(result);
                continue;
            }
            logger.info("表 {}.{} 的实际字段列表: {}", sourceDbName, sourceTableName, actualColumns);

            // 拼接字段列表字符串，用于计算 MD5 摘要
            String columnsStr = String.join(", ", actualColumns);
            logger.info("使用字段列表: {}", columnsStr);

            // 进行随机抽样 MD5 校验（注意：内部方法会分别切换数据源）
            boolean sampleMatch = verifySampleMd5Consistency(config, actualColumns, currentTime, sampleCount, message);
            result.setSampleMatch(sampleMatch);
            if (sampleMatch) {
                logger.info("表 {}.{} 随机抽样记录 MD5 校验成功。", sourceDbName, sourceTableName);
                message.append("表[").append(sourceTableName)
                        .append("]随机抽样记录 MD5 校验成功");
            }
            result.setMessage(message.toString());
            resultList.add(result);
        }
        return resultList;
    }

    /**
     * 随机抽样校验源库和归档库中记录的 MD5 摘要是否一致
     */
    private boolean verifySampleMd5Consistency(MigrationConfig config, List<String> actualColumns, Date currentTime, int sampleCount, StringBuilder message) {
        String sourceDbName = config.getSourceDbName();
        String sourceTableName = config.getSourceTableName();
        String targetDbName = config.getTargetDbName();
        String targetTableName = config.getTargetTableName();
        String timeStampColumn = config.getTimeStamp();

        // 获取主键范围 - 源库
        MinMaxVO minMaxVO = getSourceMinMax(sourceDbName, sourceTableName, timeStampColumn, currentTime, config.getSourceDatasource());
        if (minMaxVO == null || minMaxVO.getMinId() == null || minMaxVO.getMaxId() == null) {
            message.append("指定时间范围内源库表 ").append(sourceTableName).append(" 没有数据或未能获取主键范围。");
            logger.error(message.toString());
            return false;
        }
        Long minId = minMaxVO.getMinId();
        Long maxId = minMaxVO.getMaxId();

        boolean sampleMatch = true;
        Random random = new Random();
        for (int i = 0; i < sampleCount; i++) {
            long randomId = minId + (long) (random.nextDouble() * (maxId - minId + 1));
            logger.info("随机抽样第 {} 次, 随机 id: {}", i + 1, randomId);

            DataRecord sourceRecord = getSourceRecord(sourceDbName, sourceTableName, randomId, config.getSourceDatasource());
            DataRecord archiveRecord = getArchiveRecord(targetDbName, targetTableName, randomId, config.getTargetDatasource());
            if (sourceRecord == null || archiveRecord == null) {
                message.append("随机 id [").append(randomId).append("] 未查询到对应记录；");
                sampleMatch = false;
                logger.warn("随机 id {} 对应记录不存在", randomId);
                continue;
            }

            // 从实际字段中随机选取 3 个字段（不足 3 个则全部使用）
            List<String> columnsToCompare = getRandomColumns(actualColumns);
            Collections.sort(columnsToCompare);
            String columnsSelected = String.join(", ", columnsToCompare);
            logger.info("随机抽样使用的字段列表: {}", columnsSelected);

            String sourceMd5 = getSourceRowMd5(sourceDbName, sourceTableName, sourceRecord.getId(), columnsSelected, config.getSourceDatasource());
            String archiveMd5 = getArchiveRowMd5(targetDbName, targetTableName, archiveRecord.getId(), columnsSelected, config.getTargetDatasource());
            logger.info("源库记录 id={} 的 MD5: {}", sourceRecord.getId(), sourceMd5);
            logger.info("归档库记录 id={} 的 MD5: {}", archiveRecord.getId(), archiveMd5);

            if (sourceMd5 == null || archiveMd5 == null || !sourceMd5.equals(archiveMd5)) {
                message.append("记录 id [").append(sourceRecord.getId())
                        .append("]（字段 [").append(columnsSelected)
                        .append("]）摘要不匹配；");
                sampleMatch = false;
                logger.error("记录 id {} 字段 [{}] 摘要不匹配, 源库 MD5={}, 归档库 MD5={}",
                        sourceRecord.getId(), columnsSelected, sourceMd5, archiveMd5);
            }
        }
        return sampleMatch;
    }


    private List<String> getSourceColumns(String schemaName, String tableName, String sourceDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(sourceDatasource);
        List<String> cols = tableMetaMapper.getColumnNames(schemaName, tableName);
        logger.debug("getSourceColumns执行，数据库: {}, 表: {}, 返回字段: {}", schemaName, tableName, cols);
        return cols;
    }

    private MinMaxVO getSourceMinMax(String sourceDbName, String sourceTableName, String timeStampColumn, Date currentTime, String sourceDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(sourceDatasource);
        MinMaxVO vo = sourceMapper.getMinMaxId(sourceDbName, sourceTableName, timeStampColumn, currentTime);
        logger.debug("getSourceMinMax执行，数据库: {}, 表: {}, 条件: {} <= {}, 返回: {}",
                sourceDbName, sourceTableName, timeStampColumn, currentTime, vo);
        return vo;
    }

    private long getSourceCount(String sourceDbName, String sourceTableName, String timeStampColumn, Date currentTime, String sourceDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(sourceDatasource);
        long count = sourceMapper.countByTimeStamp(sourceDbName, sourceTableName, timeStampColumn, currentTime);
        logger.debug("getSourceCount执行，数据库: {}, 表: {}, 条件: {} <= {}, 返回 count: {}",
                sourceDbName, sourceTableName, timeStampColumn, currentTime, count);
        return count;
    }

    private long getArchiveCount(String schemaName, String tableName, String timeStampColumn, Date currentTime, String targetDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(targetDatasource);
        long count = archiveMapper.countByTimeStamp(schemaName, tableName, timeStampColumn, currentTime);
        logger.debug("getArchiveCount执行，数据库: {}, 表: {}, 条件: {} <= {}, 返回 count: {}",
                schemaName, tableName, timeStampColumn, currentTime, count);
        return count;
    }

    private DataRecord getSourceRecord(String schemaName, String tableName, long id, String sourceDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(sourceDatasource);
        DataRecord record = sourceMapper.getRecordByIdGreaterOrEqual(schemaName, tableName, id);
        logger.debug("getSourceRecord执行，数据库: {}, 表: {}, 随机 id: {}, 返回记录: {}",
                schemaName, tableName, id, record);
        return record;
    }

    private DataRecord getArchiveRecord(String schemaName, String tableName, long id, String targetDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(targetDatasource);
        DataRecord record = archiveMapper.getRecordByIdGreaterOrEqual(schemaName, tableName, id);
        logger.debug("getArchiveRecord执行，数据库: {}, 表: {}, 随机 id: {}, 返回记录: {}",
                schemaName, tableName, id, record);
        return record;
    }

    private String getSourceRowMd5(String schemaName, String tableName, long id, String columns, String sourceDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(sourceDatasource);
        String md5 = sourceMapper.getRowMd5(schemaName, tableName, id, columns);
        logger.debug("getSourceRowMd5执行，数据库: {}, 表: {}, id: {}, columns: {}, 返回 MD5: {}",
                schemaName, tableName, id, columns, md5);
        return md5;
    }

    private String getArchiveRowMd5(String schemaName, String tableName, long id, String columns, String targetDatasource) {
        DynamicDataSourceHolder.setDynamicDataSourceKey(targetDatasource);
        String md5 = archiveMapper.getRowMd5(schemaName, tableName, id, columns);
        logger.debug("getArchiveRowMd5执行，数据库: {}, 表: {}, id: {}, columns: {}, 返回 MD5: {}",
                schemaName, tableName, id, columns, md5);
        return md5;
    }

    private List<String> getRandomColumns(List<String> availableColumns) {
        if (availableColumns.size() <= 3) {
            logger.info("实际字段数小于或等于3个，使用全部字段: {}", availableColumns);
            return availableColumns;
        }
        List<String> copy = new ArrayList<>(availableColumns);
        Collections.shuffle(copy);
        List<String> selected = copy.subList(0, 3);
        logger.info("随机抽取的字段列表: {}", selected);
        return selected;
    }
}
